// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

#import "RIBCommandList.h"
#import "RIBCommand.h"

#import "WW3DShape.h"
#import "WWSceneClock.h"
#import "WW3DAttributeState.h"
#import "usefulWW3DFunctions.h"

@implementation RIBCommandList

- init
{
  [super init];
  eveCode = NULL;
  dirtyBoundingBox = TRUE;

  return self;

}

- awake
{
  [super awake];

  eveCode = NULL;
  dirtyBoundingBox = TRUE;

  return self;
}

- free
{
  //NXLogError("RIBCommandList %d being free'ed\n", self);
  //NXLogError("RIBCommandList: freeing %p", self);
  [self freeObjects];  
  if (eveCode)
  {  free(eveCode);
  }
  return [super free];
}

// override all list insertion and deletion routines to mark myself dirty
- addObject:anObject { dirtyBoundingBox = TRUE; return [super addObject:anObject]; }
- insertObject:anObject at:(unsigned)index { dirtyBoundingBox = TRUE; return [super insertObject:anObject at:(unsigned)index]; }
- removeObjectAt:(unsigned)index { dirtyBoundingBox = TRUE; return [super removeObjectAt:(unsigned)index]; }
- removeLastObject { dirtyBoundingBox = TRUE; return [super removeLastObject]; }
- replaceObjectAt:(unsigned)index with:newObject { dirtyBoundingBox = TRUE; return [super replaceObjectAt:(unsigned)index with:newObject]; }
- appendList: (List *)otherList { dirtyBoundingBox = TRUE; return [super appendList: (List *)otherList]; }
- addObjectIfAbsent:anObject { dirtyBoundingBox = TRUE; return [super addObjectIfAbsent:anObject]; }
- removeObject:anObject  { dirtyBoundingBox = TRUE; return [super removeObject:anObject]; }
- replaceObject:anObject with:newObject  { dirtyBoundingBox = TRUE; return [super replaceObject:anObject with:newObject]; }
- empty  { dirtyBoundingBox = TRUE; return [super empty]; }

- (const char *)eveCode { return eveCode; }
- setEveCode:(char *)newEveCode
{
  if (eveCode)
  {  free(eveCode);
  }
  if (newEveCode)
  {  eveCode = NXCopyStringBuffer(newEveCode);
  } 
  else
  {  eveCode = NULL;
  } 
  return self;
}

- _setEveCode:(char *)newEveCode
{
  if (newEveCode)
  {  eveCode = NXCopyStringBuffer(newEveCode);
  } 
  else
  {  eveCode = NULL;
  } 
  return self;
}

- setEveCodeArgc:(int)argc argv:(char **)argv
{
  int   i, howMany = 1;
  char  *tmpEveCode, *ptr;


  for (i = 0; i < argc; i++)
  {  howMany += 3 + strlen(argv[i]);
  }
  ptr = tmpEveCode = (char *)calloc(howMany, 1);

  sprintf(ptr, "%s ", argv[0]);
  ptr += strlen(argv[0]) + 1;

  for (i = 1; i < argc; i++)
  {  sprintf(ptr, "{%s} ", argv[i]);
     ptr += strlen(argv[i]) + 3;
  }

  [self setEveCode:tmpEveCode];

  free(tmpEveCode);

  return self;
}

// okay, usually "copy" doesn't actually do a deep copy.
// but, in the case of this subclass, we're gonna.
- copyFromZone:(NXZone *)zone
{
  id   newCopy = [super copyFromZone:zone];
  int  i, howMany = [self count];
  

  [newCopy _setEveCode:eveCode];
  for (i = 0; i < howMany; i++)
  {  [newCopy replaceObjectAt:i with:[[self objectAt:i] copyFromZone:zone]];
  }
  return newCopy;
}


// need to walk down my RIBCommands, asking each one...
- (BOOL)hasBoundingBox 
{  
  int   howMany, i = 0;
  BOOL  found = NO;


  howMany = [self count];
  while (!found && (i < howMany))
  {  if ([[self objectAt:i] hasBoundingBox])
     {  found = YES;
     }
     i++;
  }
  return found;
}

- (BOOL)isLerpable { return YES; }

// need to walk down my RIBCommands, asking each one...
- (BOOL)pushesCTM
{  
  int   howMany, i = 0;
  int   pushes = 0, 
        pops = 0;


  howMany = [self count];
  for (i = 0; i < howMany; i++)
  {  if ([[self objectAt:i] pushesCTM])
     {  pushes++; 
     }
     if ([[self objectAt:i] popsCTM])
     {  pops++; 
     }
  }
  if (pushes > pops)
  {  return YES;
  }
  return NO;
}

- (BOOL)popsCTM
{  
  int   howMany, i = 0;
  int   pushes = 0, 
        pops = 0;


  howMany = [self count];
  for (i = 0; i < howMany; i++)
  {  if ([[self objectAt:i] pushesCTM])
     {  pushes++; 
     }
     if ([[self objectAt:i] popsCTM])
     {  pops++; 
     }
  }
  if (pops > pushes)
  {  return YES;
  }
  return NO;
}

- (BOOL)pushesOrPopsCTM
{  
  int   howMany, i = 0;
  int   pushes = 0, 
        pops = 0;


  howMany = [self count];
  for (i = 0; i < howMany; i++)
  {  if ([[self objectAt:i] pushesCTM])
     {  pushes++; 
     }
     if ([[self objectAt:i] popsCTM])
     {  pops++; 
     }
  }
  if (pushes != pops)
  {  return YES;
  }
  return NO;
}

- (float)lastSampleIsAt { return 0.0; }


- (unsigned long int)maxSampleBandwidth
{
   unsigned long int  maxSampleBandwidth = 50;  //WAVE FIX ME
   int      i, howMany = [self count];


  for (i = 0; i < howMany; i++)
  {  maxSampleBandwidth += [(id <WWRenderable>)[self objectAt:i] maxSampleBandwidth];
  }

  return maxSampleBandwidth;
}


// need to walk down my RIBCommands, asking each one...
- (BOOL)isMotionBlurrable 
{  
  int   howMany, i = 0;
  BOOL  found = NO;


  howMany = [self count];
  while (!found && (i < howMany))
  {  if ([[self objectAt:i] isMotionBlurrable])
     {  found = YES;
     }
     i++;
  }
  return found;
}

- (BOOL)isCompoundCommand { return YES; }


// need to walk down my RIBCommands, asking each one...
// no, unfortunately, it's not as easy as that.  
// This is actually much more like a WW3DShape.  
// we might have transformative commands inside the list.
// what if we had a RIBScale and a RIBSphere object?
// we need to keep a notion of a CTM inside the list, and
// apply it to both the bounding box thus far and the current
// command
// NOTE: the changes haven't been made yet...

- (int)findBoundingBoxFromRIBCommands:(int)startingIndex using:attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtBound   tmpBoundingBox;
  RtMatrix  tmpCTM;
  int       i = startingIndex,
            incr,
            howMany = [self count];
  id        cmd;
  id        newAttributeState;


  // first we need to find a bounding box to start with
  while (i < howMany)
  {  cmd = [self objectAt:i];

     // if the cmd pops the attribute stack, we're done.
     // we return how many commands we've gone through in our time here
     if ([cmd popsCTM])
     {  i++;
        return (i - startingIndex);
     }

     if ([cmd pushesCTM])  // here we go again...
     {  newAttributeState = [[WW3DAttributeState alloc] init];
        i++;
        incr = [self findBoundingBoxFromRIBCommands:i using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        // we now need to merge this sub-attribute block into our current one
        if ([newAttributeState hasBoundingBox])
        {  // This means that the bounding box in newAttributeState needs to be
           // transformed by the ctm of attributeState, and then  we need to 
           // "grow" to the current bound with that tranformed sub-bound.

           [attributeState getTransformMatrix:tmpCTM];
          WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, [newAttributeState boundingBox], tmpCTM);
          [attributeState growBoundingBox:&tmpBoundingBox];
       }
       else
       {  NXLogError("warning: RIBCommandList <%s> has a moot set of commands at indices %d to %d\n", [myShape shapeName], (i - 1), (i + incr - 1));
          NXLogError("\tif there was a LightSource or AreaLight source in there, you can ignore this warning, though...\n");
       }
       i += incr;
     }
     else 
     {  [cmd transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        i++;
     }

    if ([cmd hasBoundingBox])
    {  // great! we grow the bound of the current attribute state
       [attributeState growBoundingBox:[cmd boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
    }

  }
  return (i - startingIndex);
}



- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  
{  
  int  i;
  id   newAttributeState;


  if (![self hasBoundingBox])
  {  return self;  // I don't have a bounding box!
  }

  // I have a bounding box; but I'm not sure what to set it to
  // initially...  I'll use the bounding box of the first 
  // RIBCommand I have that has a bounding box.
  newAttributeState = [[WW3DAttributeState alloc] init];
  i = [self findBoundingBoxFromRIBCommands:0 using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
  if (i != [self count])
  {  NXLogError("warning: RIBCommandList in shape <%s> has an unbalanced attribute delimiter at command %d (expected %d)\n", 
                [myShape shapeName], i, [self count]);
     return nil;
  }

  if ([newAttributeState hasBoundingBox])
  {  N3D_CopyBound(*([newAttributeState boundingBox]), boundingBox);
  }
  else
  {  NXLogError("warning: RIBCommandList in shape <%s> thought it had a bounding box, but it seems it doesn't...\n",
                [myShape shapeName]);
     return nil;
  }
  return self;
}


- setBoundingBox:(RtBound *)newBoundingBox
{
  N3D_CopyBound(*newBoundingBox, boundingBox);

  return self;
}

- (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStartTime endingAt:(RtFloat)intervalEndTime
{ 
   if (dirtyBoundingBox) 
   {  [self calculateBoundingBoxStartingAt:intervalStartTime endingAt:intervalEndTime];
   }
   return &boundingBox; 
}

- setMyShape:shape
{
  int   howMany = [self count], 
        i = 0;


  myShape = shape;  
  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[self objectAt:i] setMyShape:myShape];
  }
  return self; 
}

- shape { return myShape; }

- (unsigned long int)dataBandwidth
{
   int   howMany = [self count], 
         i = 0;
   unsigned 
       long int dataBandwidth = 0;


  for (i = 0; i < howMany; i++)
  {  dataBandwidth += [(id <WWRenderable>)[self objectAt:i] dataBandwidth];
  }
  
  return dataBandwidth;
}


// note: because we've made the WWSampleList "safe" for having
// multiple samples with the same data, it's perfectly valid to return
// yourself or b
- lerpWith:b by:(float)uValue
{
   id  newMe = nil, newElement;
   int i, howMany = [self count];


   if (([self class] != [b class]) || (uValue <= 0.0) || (howMany != [b count]))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   newMe = [self copyFromZone:[self zone]];
   [newMe empty];

   for (i = 0; i < howMany; i++)
   {  newElement = [[self objectAt:i] copyFromZone:[self zone]];
      if ([newElement isLerpable])
      {  [newElement lerpSelfWith:[b objectAt:i] by:uValue];
      }
      [newMe insertObject:newElement at:i];
   }

   return newMe;
}

- lerpSelfWith:b by:(float)uValue
{
   id  element;
   int i, howMany = [self count];


   if (([self class] != [b class]) || (uValue <= 0.0) || (howMany != [b count]))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   for (i = 0; i < howMany; i++)
   {  element = [self objectAt:i];
      [element lerpSelfWith:[b objectAt:i] by:uValue];
   }

   return self;
}


- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  int   howMany = [self count], 
        i = 0;


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[self objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  }
  
  return self;
}

- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
}

- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int   howMany = [self count], 
        i = 0;


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[self objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  
  return self;
}

- renderMaps:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int   howMany = [self count], 
        i = 0;


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[self objectAt:i] renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  
  return self;
}

- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int                howMany = [self count], 
                     i = 0;
  id <WWRenderable>  obj;


  if (NXDrawingStatus == NX_DRAWING) 
  {  for (i = 0; i < howMany; i++)
     {  [(id <WWRenderable>)[self objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  else
  {  for (i = 0; i < howMany; i++) // do the moot check
     {  obj = (id <WWRenderable>)[self objectAt:i];
        if (![obj isMoot])
        {  [obj renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
     }
  }

  return self;
}

- renderSelf:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  return [self preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- preRenderSelf:(WW3DCamera *)camera 
{
  return [self renderSelf:camera];
}


- (int)spinThroughSubAttributeBlocks:(int)startingIndex
{
  int       i = startingIndex,
            howMany = [self count];
  id        cmd;


  while (i < howMany)
  {  cmd = [self objectAt:i];

     // if the cmd pops the attribute stack, we're done.
     // we return how many commands we've gone through in our time here
     if ([cmd popsCTM])
     {  i++;
        return (i - startingIndex);
     }

     if ([cmd pushesCTM])  // here we go again...
     {  i++;
        i += [self spinThroughSubAttributeBlocks:i];
     }
     else 
     {  i++;
     }
  }
  return (i - startingIndex);
}

- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{ 
  int   howMany = [self count], 
        i = 0;
  id    cmd;


  while (i < howMany)
  {  cmd = [self objectAt:i];
     if ([cmd pushesCTM])
     {  //okay, recursively spin until we pop or finish...
        i++;
        i += [self spinThroughSubAttributeBlocks:0];
     }
     else
     {  [(id <WWRenderable>)[self objectAt:i++] transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime]; 
     }
  }
  
  return self;

}

// WavesWorld archiving:
// writeEve:(NXStream *)stream
// writeScene:(NXStream *)stream

- oldWriteEve:(NXStream *)stream atTabLevel:(int)tab
{
   int   howMany = [self count], 
         i;


   for (i = 0; i < howMany; i++)
   {  [(id <WWRenderable>)[self objectAt:i] writeEve:stream atTabLevel:tab];
   }
   return self;
}

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   if (!eveCode)
   {  NXPrintf(stream, "# this RIBCommandList had no eveCode...");
   }
   else
   {  NXPrintf(stream, "%s", eveCode);
   }

   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   return [self writeEve:stream atTabLevel:tab];
}

- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   int   howMany = [self count], 
         i;


   for (i = 0; i < howMany; i++)
   {  [(id <WWRenderable>)[self objectAt:i] write3DTextScene:stream atTabLevel:tab index:index++ time:time until:lastTime];
      // really need to be moving the X coordinate by the X bound of each, and make it cumulative...
   }
   return self;
}

- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   int                howMany = [self count], 
                      i, newTab = tab + 1;
   id <WWRenderable>  aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# RIBCommandList:\n");
   for (i = 0; i < (howMany-1); i++)
   {  aCmd = [self objectAt:i];
      if (![aCmd isMootStartingAt:currentTime endingAt:currentTime])
      {  if ([aCmd popsCTM])
         {  newTab--;
         }
         [aCmd writeInventorAtTime:currentTime to:stream atTabLevel:newTab];
         if ([aCmd pushesCTM])
         {  newTab++;
         }
         NXPrintf(stream, "\n");
      }
   }

   aCmd = [self objectAt:i];
   if (![aCmd isMootStartingAt:currentTime endingAt:currentTime])
   {  [(id <WWRenderable>)[self objectAt:i] writeInventorAtTime:currentTime to:stream atTabLevel:newTab];
   }
   return self;
}

- (BOOL)theSameAs:otherRIBCommand
{
  // make the default NO...  
  return NO;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}


- (BOOL)isMoot
{
   int  i, howMany = [self count];
 

   for (i = 0; i < howMany; i++)
   {  if (![(id <WWRenderable>)[self objectAt:i] isMoot])
      {  return NO;
      }
   }
   // if there are no commands, or they're all moot, then so am i...
   return YES;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }

// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }

@end
